﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Linq;

namespace ConsoleApplication1
{
    public class SoapHelper
    {
        #region Instance

        private static SoapHelper _instance;

        public static SoapHelper Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new SoapHelper();
                }
                return _instance;
            }
            set { _instance = value; }
        }


        #endregion
        #region Private members

        private string _soapEnvelope =
                    @"<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>
                        <s:Body s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' 
                                xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' 
                                xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
                            <q1:call xmlns:q1='urn:Magento'>
                                <sessionId xsi:type='xsd:string'>{0}</sessionId>
                                <resourcePath xsi:type='xsd:string'>{1}</resourcePath>
                                <args xsi:type='xsd:string'/>
                            </q1:call>
                        </s:Body>
                    </s:Envelope>";

        private string _url = "http://www.purrfectbags.co.uk/index.php/api/soap/index/ ";

        private XmlDocument CreateSoapEnvelope(string content)
        {
            // create an empty soap envelope
            XmlDocument soapEnvelopeXml = new XmlDocument();
            soapEnvelopeXml.LoadXml(content);

            return soapEnvelopeXml;
        }
        private HttpWebRequest CreateWebRequest(string url, string action = "urn:Mage_Api_Model_Server_HandlerAction")
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
            webRequest.Headers.Add("SOAPAction", action);
            webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.Accept = "text/xml";
            webRequest.Method = "POST";
            return webRequest;
        }
        private void InsertSoapEnvelopeIntoWebRequest(XmlDocument soapEnvelopeXml, HttpWebRequest webRequest)
        {
            using (Stream stream = webRequest.GetRequestStream())
            {
                soapEnvelopeXml.Save(stream);
            }
        }
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Call magento service method and return xml result.
        /// </summary>
        /// <param name="sessionId">The session id.</param>
        /// <param name="resourcePath">The resource path.</param>
        /// <returns></returns>
        public XDocument Call(string sessionId, string resourcePath)
        {
            string soapContent = string.Format(_soapEnvelope, sessionId, resourcePath);

            XmlDocument soapEnvelopeXml = CreateSoapEnvelope(soapContent);
            HttpWebRequest webRequest = CreateWebRequest(_url);
            InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);

            // begin async call to web request.
            IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);

            // suspend this thread until call is complete.
            asyncResult.AsyncWaitHandle.WaitOne();

            // get the response from the completed web request.
            string soapResult;
            using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
            using (StreamReader rd = new StreamReader(webResponse.GetResponseStream()))
            {
                soapResult = rd.ReadToEnd();
            }
            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml(soapResult);

            using (var nodeReader = new XmlNodeReader(xDoc))
            {
                nodeReader.MoveToContent();
                return XDocument.Load(nodeReader);
            }
        }

        /// <summary>
        /// Calls to list. make a call to magento and convert xml result into strongly type List<T>
        /// </summary>
        /// <typeparam name="T">Type of object to get from xml</typeparam>
        /// <param name="sessionId">The session id.</param>
        /// <param name="resourcePath">The resource path.</param>
        /// <returns>Strongly type list from xml result set.</returns>
        public List<T> CallToList<T>(string sessionId, string resourcePath)
        {
            XDocument soapResult = Call(sessionId, resourcePath);
            var items = soapResult.Descendants("item")
                            .Where(item => item.Attributes().Where(attribute => attribute.Value == "ns2:Map").Any());

            List<T> lst = new List<T>();
            T obj;
            PropertyInfo[] properties;
            string key, value;
            foreach (var customerNode in items)
            {
                obj = Activator.CreateInstance<T>();
                properties = obj.GetType().GetProperties();

                foreach (XNode property in customerNode.Nodes())
                {
                    value = key = string.Empty;

                    XElement customer = ((XElement)property);
                    var keyNode = customer.Nodes().Where(o => ((XElement)o).Name == "key");
                    var valueNode = customer.Nodes().Where(o => ((XElement)o).Name == "value");

                    if (keyNode.Any() && valueNode.Any())
                    {
                        key = ((XElement)keyNode.FirstOrDefault()).Value;
                        value = ((XElement)valueNode.FirstOrDefault()).Value;
                        if (!string.IsNullOrEmpty(key))
                        {
                            var propertyByKey = properties.Where(o => o.Name == key);
                            if (propertyByKey.Any())
                            {
                                propertyByKey.FirstOrDefault().SetValue(obj, value);
                            }
                        }
                    }
                }

                lst.Add(obj);
            }

            return lst;
        }
        #endregion
    }
}
